package org.zhaya.sdk.processors;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.logz.sawmill.Doc;
import io.logz.sawmill.ProcessResult;
import io.logz.sawmill.Processor;
import io.logz.sawmill.annotations.ProcessorProvider;
import io.logz.sawmill.exceptions.ProcessorConfigurationException;
import io.logz.sawmill.utilities.Grok;
import io.logz.sawmill.utilities.JsonUtils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * @Author: jixing
 * @Date: 2022/1/23 5:09 PM
 * @desc: 基于sawmill-core包2.0.15版本修改
 */
@Slf4j
@ProcessorProvider(type = "grok", factory = GrokProcessor.Factory.class)
public class GrokProcessor implements Processor {
    private static final String HASH = "#";
    private final String field;
    private final List<String> expressions;
    private final List<Grok> groks;
    private final List<String> overwrite;
    private final boolean ignoreMissing;
    private final List<String> tagsOnFailure;

    public GrokProcessor(String field, List<String> matchExpressions, Map<String, String> patternsBank, List<String> overwrite, boolean ignoreMissing, List<String> tagsOnFailure, Map<String, String> externalPatterns) {
        Preconditions.checkState(CollectionUtils.isNotEmpty(matchExpressions), "patterns cannot be empty");
        this.field = Objects.requireNonNull(field, "field cannot be null");
        this.expressions = matchExpressions;
        this.overwrite = CollectionUtils.isNotEmpty(overwrite) ? overwrite : Collections.emptyList();
        this.ignoreMissing = ignoreMissing;
        this.tagsOnFailure = tagsOnFailure;
        this.groks = Lists.newArrayList();
        this.compileExpressions(matchExpressions, patternsBank, externalPatterns);
    }

    private void compileExpressions(List<String> matchExpressions, Map<String, String> patternsBank, Map<String, String> externalPatterns) {
        // 将原有的规则放入newMap中，如果有自定义规则也放入newMap中
        Map<String, String> newMap = new HashMap<>(patternsBank);
        if (MapUtils.isNotEmpty(externalPatterns)) {
            newMap.putAll(externalPatterns);
        }
        matchExpressions.forEach((expression) -> {
            Grok grok;
            try {
                grok = new Grok(newMap, expression);
            } catch (RuntimeException var5) {
                throw new ProcessorConfigurationException(String.format("Failed to create grok for expression [%s]", expression), var5);
            }

            this.groks.add(grok);
        });
    }

    @Override
    public ProcessResult process(Doc doc) throws InterruptedException {
        if (!doc.hasField(this.field, String.class)) {
            return this.ignoreMissing ? ProcessResult.success() : ProcessResult.failure(String.format("failed to grok field in path [%s], field is missing or not instance of [%s]", this.field, String.class));
        } else {
            String fieldValue = doc.getField(this.field);
            List<Grok.Match> matches = this.getMatches(fieldValue);
            if (CollectionUtils.isEmpty(matches)) {
                doc.appendList("tags", this.tagsOnFailure);
                return ProcessResult.failure(String.format("failed to grok field [%s] in path [%s], doesn't match any of the expressions [%s]", fieldValue, this.field, this.expressions));
            } else {
                matches.stream().filter((match) -> CollectionUtils.isNotEmpty(match.getValues())).forEach((match) -> {
                    String field = match.getName();
                    List<Object> matchValues = match.getValues();
                    Object value = this.getValue(matchValues);
                    if (!this.overwrite.contains(field) && doc.hasField(field)) {
                        doc.appendList(field, value);
                    } else {
                        doc.addField(field, value);
                    }

                });
                return ProcessResult.success();
            }
        }
    }

    private Object getValue(List<Object> matchValues) {
        return matchValues.size() == 1 ? matchValues.get(0) : matchValues;
    }

    private List<Grok.Match> getMatches(String value) throws InterruptedException {
        Iterator<Grok> var2 = this.groks.iterator();

        List<Grok.Match> captures;
        do {
            if (!var2.hasNext()) {
                return Collections.emptyList();
            }

            Grok grok = var2.next();
            captures = grok.matches(value);
        } while (CollectionUtils.isEmpty(captures));

        return captures;
    }

    @Data
    @Builder
    @AllArgsConstructor
    public static class Configuration implements Processor.Configuration {
        private String field;
        private List<String> patterns;
        private List<String> overwrite;
        private String externalPatterns;
        private boolean ignoreMissing;
        private List<String> tagsOnFailure;

        public Configuration() {
            this.overwrite = Collections.emptyList();
            this.ignoreMissing = true;
            this.tagsOnFailure = Collections.singletonList("_grokparsefailure");
        }

    }

    public static class Factory implements Processor.Factory {
        private static final String[] PATTERN_NAMES = new String[]{"gpfs", "grok-patterns", "haproxy", "java", "linux-syslog", "mcollective", "mcollective-patterns", "mongodb", "nagios", "postgresql", "redis", "ruby", "SYSLOG5424BASEOLDER", "firewalls"};
        private final Map<String, String> patternsBank;
        private Map<String, String> externalPatternsBank;

        public Factory() {
            this.patternsBank = this.loadBuiltinPatterns();
        }

        public Factory(String dirPath) {
            File patternsDirectory = FileUtil.file(dirPath);
            this.patternsBank = this.loadExternalPatterns(patternsDirectory);
        }

        public Map<String, String> loadBuiltinPatterns() {
            Map<String, String> builtinPatterns = Maps.newHashMap();

            for (String pattern : PATTERN_NAMES) {
                try (InputStream is = this.getClass().getResourceAsStream("/grok/patterns/" + pattern)) {

                    try {
                        this.loadPatterns(builtinPatterns, is);
                    } catch (Throwable e) {
                        log.error(e.getMessage(), e);
                        throw e;
                    }
                } catch (Exception var19) {
                    throw new RuntimeException(String.format("failed to load pattern file [%s]", pattern), var19);
                }
            }

            return Collections.unmodifiableMap(builtinPatterns);
        }

        private Map<String, String> loadExternalPatterns(File dir) {
            Map<String, String> externalPatterns = Maps.newHashMap();
            String[] patternFiles = dir.list();

            for (String patternFileName : patternFiles) {
                try (FileInputStream is = new FileInputStream(dir.getPath() + StrUtil.SLASH + patternFileName)) {
                    try {
                        this.loadPatterns(externalPatterns, is);
                    } catch (Throwable e) {
                        log.error(e.getMessage(), e);
                        throw e;
                    }
                } catch (Exception var21) {
                    throw new RuntimeException(String.format("failed to load pattern file [%s]", patternFileName), var21);
                }
            }

            return Collections.unmodifiableMap(externalPatterns);
        }

        private void loadPatterns(Map<String, String> patternBank, InputStream inputStream) throws IOException {
            try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
                String line;
                try {
                    while ((line = br.readLine()) != null) {
                        String trimmedLine = line.replaceAll("^\\s+", StringUtils.EMPTY);
                        if (!trimmedLine.startsWith(HASH) && trimmedLine.length() != 0) {
                            String[] parts = trimmedLine.split("\\s+", 2);
                            if (parts.length == 2) {
                                patternBank.put(parts[0], parts[1]);
                            }
                        }
                    }
                } catch (Throwable e) {
                    log.error(e.getMessage(), e);
                    throw e;
                }
            }

        }

        private Map<String, String> getExternalPatterns(Configuration grokConfig) {
            Map<String, String> eqGrokList = Maps.newHashMap();
            List<String> epGrok = Lists.newArrayList(grokConfig.getExternalPatterns().split("\\n"));
            epGrok.forEach(ep -> {
                String[] parts = ep.split("\\s+", 2);
                if (parts.length == 2) {
                    eqGrokList.put(parts[0], parts[1].trim());
                }
            });
            return eqGrokList;
        }

        @Override
        public GrokProcessor create(Map<String, Object> config) {
            Configuration grokConfig = JsonUtils.fromJsonMap(Configuration.class, config);

            // 增加判断是否有自定义规则的逻辑
            if (StringUtils.isNotBlank(grokConfig.getExternalPatterns())) {
                this.externalPatternsBank = this.getExternalPatterns(grokConfig);
            }

            if (CollectionUtils.isEmpty(grokConfig.getPatterns())) {
                throw new ProcessorConfigurationException("cannot create grok without any pattern");
            } else {
                return new GrokProcessor(grokConfig.getField(), grokConfig.getPatterns(), this.patternsBank, grokConfig.getOverwrite(), grokConfig.isIgnoreMissing(), grokConfig.getTagsOnFailure(), this.externalPatternsBank);
            }
        }
    }
}
